import ogre.renderer.OGRE as ogre
import PhysicsManager
from Sprite import (SpriteManager, AnimatedSprite)
from randomname import getName as getName 
import random   

from EventController import EventController

import copy

from btdefinition import (NodeType, NodeState, BTEnemyList, BTEntityEventTreeLists)
from bttraversal import (BTManager)

from EventController import (Events, EntityEvents)

from CombatSystem import *

from LevelingSystem import ProceduralLeveling


class EntityManager:
    def __init__(self, spriteManager, eventController, buildingGen):
        self.entityGroups = []
        self.spriteManager = spriteManager
        self.commands = []
        self.eventController = eventController
        self.buildingGen = buildingGen
        
        #overlayManager = ogre.OverlayManager.getSingleton()
        #create panel
        """
        self._panel = overlayManager.createOverlayElement('Panel', 'PanelName')
        self._panel.metricsMode = ogre.GMM_RELATIVE
        self._panel.setPosition(0.0, 0.0)
        self._panel.setDimensions(1.0, 1.0)
        self._panel.materialName="Core/StatsBlockCenter"


        self._textElement = ogre.TextAreaOverlayElement('MessageArea')
        self._textElement.metricsMode = ogre.GMM_PIXELS
        self._textElement.setPosition(400, 400)
        self._textElement.setDimensions(10, 10)
        self._textElement.caption = "HELLO WORLD"
        self._textElement.charHeight = 16
        self._textElement.fontName = "Console"
        self._textElement.colourBottom = ogre.ColourValue(0.3, 0.5, 0.3)
        self._textElement.colourTop = ogre.ColourValue(0.5, 0.7, 0.5)

        self._overlay = overlayManager.create('MessageOverlay')
        self._overlay.add2D(self._panel)
        self._panel.addChild(self._textElement)
        self._overlay.show() 
        """
        
        
        return
    
    def getSplineTrack(self):
        
        return self.buildingGen.getSplineTrack()
    
    def onLoadPage(self, pageCoords, splinePath):
        """
        This method is called on page load events.
        """
        #for now create some temp enemeies.
        """
        listOfEnemies = [["EyeSoreWeak", ogre.Vector3(pageCoords.x - 1.1, 0.0, -100.0)]
                          , ["EyeSoreWeak", ogre.Vector3(pageCoords.x + 0.5, 0.0, -90.0)]
                          #, ["EyeSoreStrong", ogre.Vector3(pageCoords.x, 0.0, -8.0)]]
                          ]
        """
        enemyKeys = ['EyeSoreWeak', 'EyeSoreWeak']
        listOfEnemies = []
        for i in range(0, 100):
            enemy = enemyKeys[i % 2]
            pos = ogre.Vector3(pageCoords.x - random.randrange(-1.0, 4.0), 0.0, random.randrange(-4000.0, -50.0))
            
            listOfEnemies.append([enemy, pos])
        enemySprites = self.spriteManager.createEnemies(listOfEnemies)
        enemyGroup = []
        enemyGroupId = len(self.entityGroups)
        self.enemyGroupId = enemyGroupId
        
        for enemy in enemySprites:
            degrees = ogre.Degree(-90.0).valueRadians()
            orientation = ogre.Quaternion(ogre.Radian(degrees), ogre.Vector3(0.0, 1.0, 0.0))
            enemy.orientation = orientation
            enemyGroup.append(RobotEnemy(self.buildingGen.getSplineTrack(), getName(), self, enemy, 0, self.eventController, enemyGroupId))
        
        self.entityGroups.append(enemyGroup)
        
        enemyTree = BTEnemyList()
        eventTree = BTEntityEventTreeLists()
        self.enemyBrainManager = BTManager(enemyTree, enemyGroup, self, eventTree)
        
    def executeCommands(self, entContext):
        for command in entContext.commands:
            method = getattr(entContext.entity, command[0])
            method((command[0], entContext), self)
            entContext.commands.remove(command)
            
    def reset(self):
        for enemy in self.entityGroups[self.enemyGroupId]:
            enemy.reset()
    
    def advancePlayerTest(self, direction):
        for i in self.entityGroups[self.henchmenGroupId]:
            i._Advance(0, 0.016, direction, True)
        #this is for testing now. Henchmen always advance as a group up and down the z layer.
        #Get leader!
        pos = self.entityGroups[self.henchmenGroupId][0].sprite.pos
        self.eventController.emitEvent(Events.PLAYERMOVE, {"ALL": pos})
        
    def createHenchmenEntities(self, pageCoords, physicsManager):
        
        spriteManager = self.spriteManager
        henchmenNames = ["NeiHenchmen", "RolfHenchmen"]
        NeiIsDriver = 1
        henchmenDimensions = ogre.Vector3(0.5, 0.5, 0.1)
        henchmenPos = [ogre.Vector3(pageCoords.x - 2.0, pageCoords.y, 0.0), ogre.Vector3(pageCoords.x + 2.0, pageCoords.y, 0.0)]
        henchmenGroup = []
        henchmenGroupId = len(self.entityGroups)
        self.henchmenGroupId = henchmenGroupId
        self.entityGroups.append(henchmenGroup)
        splineTrack = self.buildingGen.getSplineTrack()
        for i in range(len(henchmenNames)):
            sprite = spriteManager.getHenchmen(henchmenNames[i])
            #phyEnt = physicsManager.createHenchmen(henchmenPos[i], henchmenDimensions)
            phyEnt = None
            isDriver = False
            if i == int(NeiIsDriver):
                isDriver = True
            sprite.pos = ogre.Vector3(henchmenPos[i].x, henchmenPos[i].y, henchmenPos[i].z)
            sprite.orientation = ogre.Quaternion()
            henchmenGroup.append(HenchmenEntity(splineTrack, isDriver, henchmenNames[i], self, sprite, phyEnt, self.eventController, henchmenGroupId))
        self.entityGroups.append(henchmenGroup)      
        
        henchmenTree = BTEnemyList()
        eventTree = BTEntityEventTreeLists()
        self.henchmenBrainManager = BTManager(henchmenTree, henchmenGroup, self, eventTree)
          
        return 
    
    def findRandomTarget(self):
        #go through and find alives
        alives = []
        for i in self.entityGroups[self.henchmenGroupId]:
            if i.isAlive():
                alives.append(i)
        if len(alives) > 0:
            return alives[random.randint(0, len(alives) - 1)]
        return None
    
    def findRandomEnemy(self, requester):
        
        alives = []
        for i in self.entityGroups[self.enemyGroupId]:
            dir = i.sprite.pos - requester.sprite.pos
            normalDir = dir.normalisedCopy()
            if ogre.Vector3(0.0, 0.0, -1.0).dotProduct(normalDir) >= 0.0:
                distance = dir.length()
                if i.isAlive() and distance < 200.0:
                    alives.append(i)
        if len(alives) > 0:
            return alives[random.randint(0, len(alives) - 1)]
        return None
        
    def update(self, dt, worldController):
        
        for command in self.commands:
            method = getattr(self, command[0][0])
            method(command[1])
            self.commands.remove(command)
        self.enemyBrainManager.update()
        self.henchmenBrainManager.update()
             
        for group in self.entityGroups:
            for entity in group:
                 for command in entity.commands:
                     #self.command[0](dt, command[1].memorypad)
            
                     method = getattr(entity, "_"+command[0])
                     method(dt, command[1].memorypad)
                     entity.commands.remove(command)
                 entity.update(dt)
        return
    def ScheduledAttack(self, params):
        weapon = params[0]
        defense = params[1]
        attacker = params[2]
        defender = params[3]
        if defender.isAlive() == True:
            #dice roll and equations etc        
            damage = ResolveCombat.resolve(weapon, defense, attacker.attributes, attacker.stats, defender.attributes, defender.stats)
            defender.stats.damage(damage)
            if defender.isCriticalHealth(defender.stats):
                defender.entityEventController.emitEvent(EntityEvents.ON_CRITICAL_HEALTH, {"ALL":EntityEvents.ON_CRITICAL_HEALTH})
            #print "Attacker:" + attacker.name + "Health: " + str(attacker.stats) + " Defender:" + defender.name + "Health: " + str(defender.stats)
            
            
    def ScheduledCommand(self, vars):
        key = vars[0]
        init = vars[1]
        timer = None
        timeOnTarget = 0
        commandVars = None
        if init != "INITED":
            timer = ogre.Timer()
            timeOnTarget = vars[1]
            init = "INITED"
            commandVars = vars[2]
        else:
            timer = vars[2]
            timeOnTarget = vars[3]
            commandVars = vars[4]
        elapsedTimeSeconds = timer.getMilliseconds() / 1000.0
        if elapsedTimeSeconds >= timeOnTarget:
            del timer
            keys = [key]
            command = (keys, commandVars)
            self.commands.append(command)
            return
        
        keys = ["ScheduledCommand"]
        vars = (key, init, timer, timeOnTarget, commandVars)     
        self.commands.append((keys, vars))
        return
    def scheduleAttack(self, weapon, defense, entityAttacker, entityDefender):
        """
        Precondition: entityAttacker and entityDefender must be valid when running the schedule attack. 
        The way the system is setup, enemies won't get destroyed until paging out of the current page. Therefore enemies should always be valid when the scheduled attack is run. 
        When the system pages out enemies, it is assumed all scheduled attacks have been carried out. Why? For example, you can have a enemy that attacks with a range weapon (i.e a scheduled attack)
        and in the meantime gets killed. The attack is still valid. For melee attacks we may have a different behavior. 
        """
        key = ["ScheduledCommand"]
        #resolve the velocity of the weapon i.e time on target.
        dir = entityDefender.sprite.pos - entityAttacker.sprite.pos
        normalDir = dir.normalisedCopy()
        if entityAttacker.sprite.orientation.zAxis().dotProduct(normalDir) > 0.0:
            entityAttacker.target = None
            return
        distance = (dir).length()
        if distance >= 200.0: #this should be based on the weapon's range.
            entityAttacker.target = None
            return
        if weapon.speed >= 0: #wtf real numbers.
            timeOnTarget = distance / weapon.speed
        #put a delay based on agility
        delay = entityAttacker.attributes.agility * random.randrange(0.0, 4.0)
        entityAttacker.addAttackDelay(delay) #This should be on the weapon!
        timeOnTarget += delay
        params = ("ScheduledAttack", timeOnTarget, (weapon, defense, entityAttacker, entityDefender))
        self.commands.append((key, params))
        
        params = ("ScheduledEvent", delay, (Events.ONATTACK,  {"WorldController":(entityAttacker.weapon.weaponSystemId, entityAttacker.sprite.pos, 
                                                                                  entityDefender.sprite.pos, entityAttacker.isHenchmen())}))
        
        self.commands.append((key, params))
        
    def ScheduledEvent(self, params):
        eventType = params[0]
        paramDict = params[1]
        
        self.eventController.emitEvent(Events.ONATTACK, paramDict)      
        return
    
        
        

class Attributes:
    """
    Hack a system in place.
    """
    def __init__(self, power=1.0, agility=0.5, tech=0.5):
        self.power = power
        self.agility=agility
        self.tech = tech
        

class Stats:
    def __init__(self, level=1, health=100, magic=100):
        self.healthLevel = health
        self.magicLevel = magic
        self.level = level
    def damage(self, damage):
        self.healthLevel -= int(damage)
        #if self.healthLevel < 0:
            #self.healthLevel = 0
    def healHealth(self, percentage):
        fullHealth = ProceduralLeveling.getHealthAtLevel(self.level)
        self.healthLevel = int(percentage * float(fullHealth))
        
    def __str__(self):
        return str(self.healthLevel)


    
    
class Entity:
    def __init__(self, name, entityManager, animatedSprite, physicsEntity, eventController, groupMask, entityEventController=EventController(EntityEvents.EVENT_LIST)):
        self.name = name
        self.weapon = None
        self.attributes = Attributes()
        self.stats = Stats()
        self.sprite = animatedSprite
        self.physicsEntity = physicsEntity
        self.commands = []
        self.speed = ogre.Vector3(0.0, 0.0, 5.0)
        self.originalPos = ogre.Vector3(self.sprite.pos.x, self.sprite.pos.y, self.sprite.pos.z)
        self.target = None
        self.attackAnimation = False
        self.elapsedAttackTimer = 0.0
        self.entityManager = entityManager
        self.eventController = eventController       
        self.groupMask = groupMask
        self.entityEventController = entityEventController
        self.transAlongLocalX = self.sprite.pos.x
        self.transAlongLocalY = self.sprite.pos.y
        #self.originalPos = self.sprite.pos
        return
    
    def isAlive(self):
        if self.stats.healthLevel <= 0:
            return False
        else:
            return True
    def isCriticalHealth(self, stats):
        return ProceduralLeveling.isCriticalHealth(stats)
        
    
    def reset(self):
        if self.target is not None: #hack
            self.target.stats = Stats()
        self.target = None
        self.sprite.pos = ogre.Vector3(self.originalPos.x, self.originalPos.y, self.originalPos.z)
        self.commands = [] #SERIOUS HACK. Need to think about how to remove current commands in the system. Do we even need commands?
    
    def findRandomTarget(self):
        return None
        
    def TargetExist(self, command, entityManager):
        """
        Note this is a simulation function. memoryPad is a simulation of a data array. We're doing a protoype implementation for something we're going to implement in c++.
        More Note: this method currently does what find threat is supposed to be doing.
        """
        memoryPad = command[1].memorypad
        #alive check. There should be a behavior node for this check.
        if not self.isAlive():
            memoryPad.mem1 = NodeState.FAIL
            return
        if self.target == None:
            self.target = self.findRandomTarget(self) 
            if self.target is None:
                #memoryPad.mem1 = NodeState.FAIL
                memoryPad.mem1 = NodeState.FAIL #success in order to debug splien traversal.
        else:
            memoryPad.mem1 = NodeState.SUCCESS  
        return 
    
    def HealthPotion(self, command, entityManager):
        self.commands.append(command)
    
    def _HealthPotion(self, dt, memorypad):
        memorypad.mem0 = NodeState.SUCCESS
        return
        #check health potion
        #self.stats.healHealth(1.0)
    
    def Attack(self, command, entityManager):
        self.commands.append(command)
        return
    
    def addAttackDelay(self, delay):
        self.elapsedAttackTimer += delay
    
    def _Attack(self, dt, memorypad):
        """
        The invariance for this method is self.target should always exist and is alive!
        """
        
        if self.target == None:
            memorypad.mem0 = NodeState.SUCCESS
            return
            #raise "Target should be none invariance violated"
        if not self.target.isAlive() or not self.isAlive():
            self.target = None
            memorypad.mem0 = NodeState.SUCCESS
            return
        
        if self.isAlive():
            if self.elapsedAttackTimer <= 0.0: #This should be checked on the weapon and not on the entity!
                self.elapsedAttackTimer = self.weapon.rate 
                #worldController.emitAttackParticles(self.particleSystemId, self.sprite.pos, self.target.sprite.pos)
                self.entityManager.scheduleAttack(self.weapon, self.target.armor, self, self.target)
        memorypad.mem0 = NodeState.SUCCESS
        
    def Advance(self, command, entityManager):
        self.commands.append(command)
        return
    
    def _Advance(self, dt, memorypad):
        memorypad.mem0 = NodeState.SUCCESS
        return
    def update(self, dt):
        self.elapsedAttackTimer -= dt
        if self.isAlive():           
            self.sprite.update(dt)
    def isHenchmen(self):
        return False
 
class RobotEnemy(Entity):
    def __init__(self, splineTrack, name, entityManager, animatedSprite, physicsEntity, eventController, groupMask):
        Entity.__init__(self, name, entityManager, animatedSprite, physicsEntity, eventController, groupMask)
        self.attributes = Attributes(power=2.0)
        self.weapon = RandomWeaponAndArmor.getWeapon(self.stats.level, [-2, 0], WeaponSystemTypes.LASERAT419)
        self.armor = RandomWeaponAndArmor.getArmor(self.stats.level)
        eventController.subscribe(Events.PLAYERMOVE, self.onPlayerMove, "ALL")
        self.stopZ = 0.0
        self.stopZRange = random.randint(2.0, 25.0)
        self.splineTrack = splineTrack
        self.splineTrack.timePerNode = 4.0
        self.splineTrack.setTimeFrame(-self.sprite.pos.z / 4000.0)
        
        return  
    
    #def __getAttribute__(self, name):
     #   return getAttr(self, name)
    
    def _Advance(self, dt, memorypad):
        #advance one step. to the next layer. whatever.
        memorypad.mem0 = NodeState.SUCCESS
        
        origin, orient = self.splineTrack.getCurrentFrame() #Enemies have hard-coded direction...for now.
        self.splineTrack.advance(-dt)
        localXAxis = orient.xAxis()
        localYAxis = orient.yAxis()
        pos = origin + localXAxis * self.transAlongLocalX + localYAxis * self.transAlongLocalY
        
        self.sprite.setPosition(pos)
        
        #if self.sprite.pos.z < self.stopZ:
         #   self.sprite.pos += self.speed * dt
    def onPlayerMove(self, positionOrient):
        position = positionOrient[0]
        orient = positionOrient[1]
        
        #self.stopZ = position.z - self.stopZRange
        return
    def findRandomTarget(self, requester):
        return self.entityManager.findRandomTarget()
    
class HenchmenEntity(Entity):
    def __init__(self, splineTrack, isDriver, name, entityManager, animatedSprite, physicsEntity, eventController, groupMask):
        Entity.__init__(self, name, entityManager, animatedSprite, physicsEntity, eventController, groupMask)
        self.attributes = Attributes(power=4.0)
        self.weapon = RandomWeaponAndArmor.getWeapon(self.stats.level + 5, [0.0, 1.0], WeaponSystemTypes.LASERNBMAX)
        self.armor = RandomWeaponAndArmor.getArmor(self.stats.level)
        self.maxStepToTake = 2000000
        self.isDriver = isDriver
        self.splineTrack = splineTrack
        return
    
    def isHenchmen(self):
        return True
    
    def _Advance(self, dt, memorypad, direction=ogre.Vector3(0.0, 0.0, -20.0), ignoreStep=False):
        
        memorypad.mem0 = NodeState.SUCCESS
        
        #Get the coordinate frame along the spline track.
        origin, orient = self.splineTrack.getCurrentFrame()
        self.splineTrack.advance(dt)
        #hasNext, nextOrigin, nextOrient = self.entityManager.getSplineTrackNextCoordinateFrame(1.0)
        if True:
            localXAxis = orient.xAxis()
            localYAxis = orient.yAxis()
            pos = origin + localXAxis * self.transAlongLocalX + localYAxis * self.transAlongLocalY
            camPos = pos - localXAxis * 2.0
            self.sprite.setPosition(pos)
            #if hasNext:
            if self.isDriver:
                self.eventController.emitEvent(Events.PLAYERMOVE, {"ALL": (camPos, orient)}) #next orient is for look ahead
        if not self.splineTrack.hasNext(dt):
            print "STOPPED!!!"
        return 
        
        """
        self.sprite.updatePosition(direction * dt)
        self.maxStepToTake -= 1
        pos = self.sprite.pos
        self.eventController.emitEvent(Events.PLAYERMOVE, {"ALL": pos})
        """
        """ 
        if (self.maxStepToTake > 0) and (not ignoreStep):      
            self.sprite.updatePosition(direction)
            self.maxStepToTake -= 1
            pos = self.sprite.pos
            self.eventController.emitEvent(Events.PLAYERMOVE, {"ALL": pos})
        elif ignoreStep:
            self.sprite.updatePosition(direction)
            pos = self.sprite.pos
            self.eventController.emitEvent(Events.PLAYERMOVE, {"ALL": pos})
        """
        #return Entity._Advance(self, dt, memorypad)
    def _HealthPotion(self, dt, memorypad):
        self.stats.healHealth(1.0)
            
    def findRandomTarget(self, requester):
        return self.entityManager.findRandomEnemy(requester)
    
    